home *** CD-ROM | disk | FTP | other *** search
/ Linux Cubed Series 4: GNU Archives / Linux Cubed Series 4 - GNU Archives.iso / gnu / sharutil.2 / sharutil / sharutils-4.2 / src / unshar.c < prev    next >
Encoding:
C/C++ Source or Header  |  1995-11-21  |  11.8 KB  |  451 lines

  1. /* Handle so called `shell archives'.
  2.    Copyright (C) 1994, 1995 Free Software Foundation, Inc.
  3.  
  4.    This program is free software; you can redistribute it and/or modify
  5.    it under the terms of the GNU General Public License as published by
  6.    the Free Software Foundation; either version 2, or (at your option)
  7.    any later version.
  8.  
  9.    This program is distributed in the hope that it will be useful,
  10.    but WITHOUT ANY WARRANTY; without even the implied warranty of
  11.    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12.    GNU General Public License for more details.
  13.  
  14.    You should have received a copy of the GNU General Public License
  15.    along with this program; if not, write to the Free Software Foundation,
  16.    Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  17. */
  18.  
  19. /* Unpackage one or more shell archive files.  The `unshar' program is a
  20.    filter which removes the front part of a file and passes the rest to
  21.    the `sh' command.  It understands phrases like "cut here", and also
  22.    knows about shell comment characters and the Unix commands `echo',
  23.    `cat', and `sed'.  */
  24.  
  25. #include "system.h"
  26. #include "getopt.h"
  27.  
  28. /* Buffer size for holding a file name.  FIXME: No fix limit in GNU... */
  29. #define NAME_BUFFER_SIZE 1024
  30.  
  31. /* Buffer size for shell process input.  */
  32. #define SHELL_BUFFER_SIZE 8196
  33.  
  34. #define EOL '\n'
  35.  
  36. /* The name this program was run with. */
  37. const char *program_name;
  38.  
  39. /* If non-zero, display usage information and exit.  */
  40. static int show_help = 0;
  41.  
  42. /* If non-zero, print the version on standard output and exit.  */
  43. static int show_version = 0;
  44.  
  45. static int pass_c_flag = 0;
  46. static int continue_reading = 0;
  47. static const char *exit_string = "exit 0";
  48. static size_t exit_string_length;
  49. static char *current_directory;
  50.  
  51. /*-------------------------------------------------------------------.
  52. | Match the leftmost part of a string.  Returns 1 if initial         |
  53. | characters of DATA match PATTERN exactly; else 0.  This was         |
  54. | formerly a function.  But because we always have a constant string |
  55. | as the seconf argument and the length of the second argument is a  |
  56. | lot of shorter than the buffer the first argument is pointing at,  |
  57. | we simply use `memcmp'.  And one more point: even if the `memcmp'  |
  58. | function does not work correct for 8 bit characters it does not    |
  59. | matter here.  We are only interested in equal or not equal         |
  60. | information.                                 |
  61. `-------------------------------------------------------------------*/
  62.  
  63. #define starting_with(data, pattern)                        \
  64.   (memcmp (data, pattern, sizeof (pattern) - 1) == 0)
  65.  
  66.  
  67. /*-------------------------------------------------------------------------.
  68. | For a DATA string and a PATTERN containing one or more embedded       |
  69. | asterisks (matching any number of characters), return non-zero if the       |
  70. | match succeeds, and set RESULT_ARRAY[I] to the characters matched by the |
  71. | I'th *.                                   |
  72. `-------------------------------------------------------------------------*/
  73.  
  74. static int
  75. matched_by (data, pattern, result_array)
  76.      const char *data;
  77.      const char *pattern;
  78.      char **result_array;
  79. {
  80.   const char *pattern_cursor = NULL;
  81.   const char *data_cursor = NULL;
  82.   char *result_cursor = NULL;
  83.   int number_of_results = 0;
  84.  
  85.   while (1)
  86.     if (*pattern == '*')
  87.       {
  88.     pattern_cursor = ++pattern;
  89.     data_cursor = data;
  90.     result_cursor = result_array[number_of_results++];
  91.     *result_cursor = '\0';
  92.       }
  93.     else if (*data == *pattern)
  94.       {
  95.     if (*pattern == '\0')
  96.       /* The pattern matches.  */
  97.       return 1;
  98.  
  99.     pattern++;
  100.     data++;
  101.       }
  102.     else
  103.       {
  104.     if (*data == '\0')
  105.       /* The pattern fails: no more data.  */
  106.       return 0;
  107.  
  108.     if (pattern_cursor == NULL)
  109.       /* The pattern fails: no star to adjust.  */
  110.       return 0;
  111.  
  112.     /* Restart pattern after star.  */
  113.  
  114.     pattern = pattern_cursor;
  115.     *result_cursor++ = *data_cursor;
  116.     *result_cursor = '\0';
  117.  
  118.     /* Rescan after copied char.  */
  119.  
  120.     data = ++data_cursor;
  121.       }
  122. }
  123.  
  124. /*------------------------------------------------------------------------.
  125. | Associated with a given file NAME, position FILE at the start of the      |
  126. | shell command portion of a shell archive file.  Scan file from position |
  127. | START.                                  |
  128. `------------------------------------------------------------------------*/
  129.  
  130. static int
  131. find_archive (name, file, start)
  132.      const char *name;
  133.      FILE *file;
  134.      off_t start;
  135. {
  136.   char buffer[BUFSIZ];
  137.   off_t position;
  138.  
  139.   /* Results from star matcher.  */
  140.  
  141.   static char res1[BUFSIZ], res2[BUFSIZ], res3[BUFSIZ], res4[BUFSIZ];
  142.   static char *result[] = {res1, res2, res3, res4};
  143.  
  144.   fseek (file, start, 0);
  145.  
  146.   while (1)
  147.     {
  148.  
  149.       /* Record position of the start of this line.  */
  150.  
  151.       position = ftell (file);
  152.  
  153.       /* Read next line, fail if no more and no previous process.  */
  154.  
  155.       if (!fgets (buffer, BUFSIZ, file))
  156.     {
  157.       if (!start)
  158.         error (0, 0, _("Found no shell commands in %s"), name);
  159.       return 0;
  160.     }
  161.  
  162.       /* Bail out if we see C preprocessor commands or C comments.  */
  163.  
  164.       if (starting_with (buffer, "#include")
  165.       || starting_with (buffer, "# include")
  166.       || starting_with (buffer, "#define")
  167.       || starting_with (buffer, "# define")
  168.       || starting_with (buffer, "#ifdef")
  169.       || starting_with (buffer, "# ifdef")
  170.       || starting_with (buffer, "#ifndef")
  171.       || starting_with (buffer, "# ifndef")
  172.       || starting_with (buffer, "/*"))
  173.     {
  174.       error (0, 0, _("%s looks like raw C code, not a shell archive"),
  175.          name);
  176.       return 0;
  177.     }
  178.  
  179.       /* Does this line start with a shell command or comment.  */
  180.  
  181.       if (starting_with (buffer, "#")
  182.       || starting_with (buffer, ":")
  183.       || starting_with (buffer, "echo ")
  184.       || starting_with (buffer, "sed ")
  185.       || starting_with (buffer, "cat ")
  186.       || starting_with (buffer, "if "))
  187.     {
  188.       fseek (file, position, 0);
  189.       return 1;
  190.     }
  191.  
  192.       /* Does this line say "Cut here".  */
  193.  
  194.       if (matched_by (buffer, "*CUT*HERE*", result) ||
  195.       matched_by (buffer, "*cut*here*", result) ||
  196.       matched_by (buffer, "*TEAR*HERE*", result) ||
  197.       matched_by (buffer, "*tear*here*", result) ||
  198.       matched_by (buffer, "*CUT*CUT*", result) ||
  199.       matched_by (buffer, "*cut*cut*", result))
  200.     {
  201.  
  202.       /* Read next line after "cut here", skipping blank lines.  */
  203.  
  204.       while (1)
  205.         {
  206.           position = ftell (file);
  207.  
  208.           if (!fgets (buffer, BUFSIZ, file))
  209.         {
  210.           error (0, 0, _("Found no shell commands after `cut' in %s"),
  211.              name);
  212.           return 0;
  213.         }
  214.  
  215.           if (*buffer != '\n')
  216.         break;
  217.         }
  218.  
  219.       /* Win if line starts with a comment character of lower case
  220.          letter.  */
  221.  
  222.       if (*buffer == '#' || *buffer == ':'
  223.           || (('a' <= *buffer) && ('z' >= *buffer)))
  224.         {
  225.           fseek (file, position, 0);
  226.           return 1;
  227.         }
  228.  
  229.       /* Cut here message lied to us.  */
  230.  
  231.       error (0, 0, _("%s is probably not a shell archive"), name);
  232.       error (0, 0, _("The `cut' line was followed by: %s"), buffer);
  233.       return 0;
  234.     }
  235.     }
  236. }
  237.  
  238. /*-----------------------------------------------------------------.
  239. | Unarchive a shar file provided on file NAME.  The file itself is |
  240. | provided on the already opened FILE.                   |
  241. `-----------------------------------------------------------------*/
  242.  
  243. static void
  244. unarchive_shar_file (name, file)
  245.      const char *name;
  246.      FILE *file;
  247. {
  248.   char buffer[SHELL_BUFFER_SIZE];
  249.   FILE *shell_process;
  250.   off_t current_position = 0;
  251.   char *more_to_read;
  252.  
  253.   while (find_archive (name, file, current_position))
  254.     {
  255.       printf ("%s:\n", name);
  256.       shell_process = popen (pass_c_flag ? "sh -s - -c" : "sh", "w");
  257.       if (!shell_process)
  258.     error (EXIT_FAILURE, errno, _("Starting `sh' process"));
  259.  
  260.       if (!continue_reading)
  261.     {
  262.       size_t len;
  263.  
  264.       while ((len = fread (buffer, 1, SHELL_BUFFER_SIZE, file)) != 0)
  265.         fwrite (buffer, 1, len, shell_process);
  266. #if 0
  267.       /* Don't know whether a test is appropriate here.  */
  268.       if (ferror (shell_process) != 0)
  269.         fwrite (buffer, length, 1, shell_process);
  270. #endif
  271.       pclose (shell_process);
  272.       break;
  273.     }
  274.       else
  275.     {
  276.       while (more_to_read = fgets (buffer, SHELL_BUFFER_SIZE, file),
  277.          more_to_read != NULL)
  278.         {
  279.           fputs (buffer, shell_process);
  280.           if (!strncmp (exit_string, buffer, exit_string_length))
  281.         break;
  282.         }
  283.       pclose (shell_process);
  284.  
  285.       if (more_to_read)
  286.         current_position = ftell (file);
  287.       else
  288.         break;
  289.     }
  290.     }
  291. }
  292.  
  293. /*-----------------------------.
  294. | Explain how to use program.  |
  295. `-----------------------------*/
  296.  
  297. static void
  298. usage (status)
  299.      int status;
  300. {
  301.   if (status != EXIT_SUCCESS)
  302.     fprintf (stderr, _("Try `%s --help' for more information.\n"),
  303.          program_name);
  304.   else
  305.     {
  306.       printf (_("Usage: %s [OPTION]... [FILE]...\n"), program_name);
  307.       fputs (_("\
  308. Mandatory arguments to long options are mandatory for short options too.\n\
  309. \n\
  310.   -d, --directory=DIRECTORY   change to DIRECTORY before unpacking\n\
  311.   -c, --overwrite             pass -c to shar script for overwriting files\n\
  312.   -e, --exit-0                same as `--split-at=\"exit 0\"'\n\
  313.   -E, --split-at=STRING       split concatenated shars after STRING\n\
  314.   -f, --force                 same as `-c'\n\
  315.       --help                  display this help and exit\n\
  316.       --version               output version information and exit\n\
  317. \n\
  318. If no FILE, standard input is read.\n"),
  319.          stdout);
  320.     }
  321.   exit (status);
  322. }
  323.  
  324. /*--------------------------------------.
  325. | Decode options and launch execution.  |
  326. `--------------------------------------*/
  327.  
  328. static const struct option long_options[] =
  329. {
  330.   {"directory", required_argument, NULL, 'd'},
  331.   {"exit-0", no_argument, NULL, 'e'},
  332.   {"force", no_argument, NULL, 'f'},
  333.   {"overwrite", no_argument, NULL, 'c'},
  334.   {"split-at", required_argument, NULL, 'E'},
  335.  
  336.   {"help", no_argument, &show_help, 1},
  337.   {"version", no_argument, &show_version, 1},
  338.  
  339.   { NULL, 0, NULL, 0 },
  340. };
  341.  
  342. int
  343. main (argc, argv)
  344.      int argc;
  345.      char *const *argv;
  346. {
  347.   size_t size_read;
  348.   FILE *file;
  349.   char name_buffer[NAME_BUFFER_SIZE];
  350.   char copy_buffer[NAME_BUFFER_SIZE];
  351.   int optchar;
  352.  
  353.   program_name = argv[0];
  354.   setlocale (LC_ALL, "");
  355.  
  356.   /* Set the text message domain.  */
  357.   bindtextdomain (PACKAGE, LOCALEDIR);
  358.   textdomain (PACKAGE);
  359.  
  360. #ifdef __MSDOS__
  361.   setbuf (stdout, NULL);
  362.   setbuf (stderr, NULL);
  363. #endif
  364.  
  365.   if (current_directory = xgetcwd (), !current_directory)
  366.     error (EXIT_FAILURE, errno, _("Cannot get current directory name"));
  367.  
  368.   /* Process options.  */
  369.  
  370.   while (optchar = getopt_long (argc, argv, "E:cd:ef", long_options, NULL),
  371.      optchar != EOF)
  372.     switch (optchar)
  373.       {
  374.       case '\0':
  375.     break;
  376.  
  377.       case 'c':
  378.       case 'f':
  379.     pass_c_flag = 1;
  380.     break;
  381.  
  382.       case 'd':
  383.     if (chdir (optarg) == -1)
  384.       error (2, 0, _("Cannot chdir to `%s'"), optarg);
  385.     break;
  386.  
  387.       case 'E':
  388.     exit_string = optarg;
  389.     /* Fall through.  */
  390.  
  391.       case 'e':
  392.     continue_reading = 1;
  393.     exit_string_length = strlen (exit_string);
  394.     break;
  395.  
  396.       default:
  397.     usage (EXIT_FAILURE);
  398.       }
  399.  
  400.   if (show_version)
  401.     {
  402.       printf ("%s - GNU %s %s\n", program_name, PACKAGE, VERSION);
  403.       exit (EXIT_SUCCESS);
  404.     }
  405.  
  406.   if (show_help)
  407.     usage (EXIT_SUCCESS);
  408.  
  409.   if (optind < argc)
  410.     for (; optind < argc; optind++)
  411.       {
  412.     if (argv[optind][0] == '/')
  413.       stpcpy (name_buffer, argv[optind]);
  414.     else
  415.       {
  416.         char *cp = stpcpy (name_buffer, current_directory);
  417.         *cp++ = '/';
  418.         stpcpy (cp, argv[optind]);
  419.       }
  420.     if (file = fopen (name_buffer, "r"), !file)
  421.       error (EXIT_FAILURE, errno, name_buffer);
  422.     unarchive_shar_file (name_buffer, file);
  423.     fclose (file);
  424.       }
  425.   else
  426.     {
  427.       sprintf (name_buffer, "/tmp/unsh.%05d", (int) getpid ());
  428.       unlink (name_buffer);
  429.  
  430.       if (file = fopen (name_buffer, "w+"), !file)
  431.     error (EXIT_FAILURE, errno, name_buffer);
  432. #ifndef __MSDOS__
  433.       unlink (name_buffer);    /* will be deleted on fclose */
  434. #endif
  435.  
  436.       while (size_read = fread (copy_buffer, 1, sizeof (copy_buffer), stdin),
  437.          size_read != 0)
  438.     fwrite (copy_buffer, size_read, 1, file);
  439.       rewind (file);
  440.  
  441.       unarchive_shar_file (_("standard input"), file);
  442.  
  443.       fclose (file);
  444. #ifdef __MSDOS__
  445.       unlink (name_buffer);
  446. #endif
  447.     }
  448.  
  449.   exit (EXIT_SUCCESS);
  450. }
  451.